// OGoMojo
// Whitemice.MOGI.RPC2.Server
// License: LGPL
// Copyright: Whitemice Consulting, 2006
// Author(s): Adam Tauno Williams (awilliam@whitemice.org)
using System;
using System.Net;
using System.Web;
using System.Collections;
using Whitemice.MOGI;
using CookComputing.XmlRpc;

namespace Whitemice.MOGI.RPC2 {
	/// \brief Implements the server class to use the XML-RPC protocol
	public class Server : Whitemice.MOGI.Server, Whitemice.MOGI.IServer
	{
		protected IRPC2					serverProxy;
        protected XmlRpcClientProtocol	rpcClient;
        protected string[]				addressElements;
        protected string[]				phoneElements;
        protected Hashtable				fetchKeys;
	
		/// \brief Constructor
		public Server(string logName) : base(logName) 
		{
			this.serverProxy = (IRPC2)XmlRpcProxyGen.Create(typeof(IRPC2));
			this.rpcClient = (XmlRpcClientProtocol)this.serverProxy;
			this.rpcClient.ProtocolVersion = HttpVersion.Version10;
			this.rpcClient.PreAuthenticate = true;
			this.rpcClient.Timeout = 1800000;
			Log.Debug("RPC Proxy Created");
			Log.Debug("  HTTP Version=1.0 PreAuthenticate=true Timeout=1800000");
			addressElements = new string[9] {"id", "name1", "name2", "name3", "street", "city", "state", "zip", "country" };
			phoneElements = new string[3] { "number", "telephoneId", "info" };
		}
		
		/// \brief Populate the array of supported operations
		protected override void FillSupportedOperations()
		{
			Log.Debug("FillSupportedOperations[override]");
			((ArrayList)supportedOperations[DocumentType.Appointment]).Add(Operation.Fetch);
			((ArrayList)supportedOperations[DocumentType.Appointment]).Add(Operation.Get);
			((ArrayList)supportedOperations[DocumentType.Appointment]).Add(Operation.Put);
			((ArrayList)supportedOperations[DocumentType.Appointment]).Add(Operation.Remove);
			((ArrayList)supportedOperations[DocumentType.Contact]).Add(Operation.Get);
			((ArrayList)supportedOperations[DocumentType.Contact]).Add(Operation.Put);
			((ArrayList)supportedOperations[DocumentType.Contact]).Add(Operation.Fetch);
			((ArrayList)supportedOperations[DocumentType.Contact]).Add(Operation.Remove);
			((ArrayList)supportedOperations[DocumentType.Contact]).Add(Operation.XA);
			((ArrayList)supportedOperations[DocumentType.Contact]).Add(Operation.AccessCheck);
			((ArrayList)supportedOperations[DocumentType.Contact]).Add(Operation.AccessGrant);
			((ArrayList)supportedOperations[DocumentType.Contact]).Add(Operation.ListRelations);
			((ArrayList)supportedOperations[DocumentType.Contact]).Add(Operation.MakeRelation);
			((ArrayList)supportedOperations[DocumentType.Enterprise]).Add(Operation.Get);
			((ArrayList)supportedOperations[DocumentType.Enterprise]).Add(Operation.Put);
			((ArrayList)supportedOperations[DocumentType.Enterprise]).Add(Operation.Fetch);
			((ArrayList)supportedOperations[DocumentType.Enterprise]).Add(Operation.Remove);
			((ArrayList)supportedOperations[DocumentType.Enterprise]).Add(Operation.XA);
			((ArrayList)supportedOperations[DocumentType.Enterprise]).Add(Operation.AccessCheck);
			((ArrayList)supportedOperations[DocumentType.Enterprise]).Add(Operation.AccessGrant);
			((ArrayList)supportedOperations[DocumentType.Enterprise]).Add(Operation.ListRelations);
			((ArrayList)supportedOperations[DocumentType.Task]).Add(Operation.Fetch);
			((ArrayList)supportedOperations[DocumentType.Task]).Add(Operation.Get);
			((ArrayList)supportedOperations[DocumentType.Task]).Add(Operation.Put);
			((ArrayList)supportedOperations[DocumentType.Task]).Add(Operation.Remove);
			((ArrayList)supportedOperations[DocumentType.Task]).Add(Operation.ReadAnnotations);
			((ArrayList)supportedOperations[DocumentType.Task]).Add(Operation.MakeAnnotation);
			((ArrayList)supportedOperations[DocumentType.Resource]).Add(Operation.Get);
			((ArrayList)supportedOperations[DocumentType.Resource]).Add(Operation.Categories);
			((ArrayList)supportedOperations[DocumentType.Resource]).Add(Operation.Fetch);
			((ArrayList)supportedOperations[DocumentType.Resource]).Add(Operation.Put);
			((ArrayList)supportedOperations[DocumentType.Resource]).Add(Operation.Remove);
			((ArrayList)supportedOperations[DocumentType.Team]).Add(Operation.Get);
			((ArrayList)supportedOperations[DocumentType.Team]).Add(Operation.Put);
			((ArrayList)supportedOperations[DocumentType.Team]).Add(Operation.Fetch);
			((ArrayList)supportedOperations[DocumentType.Account]).Add(Operation.Get);
			((ArrayList)supportedOperations[DocumentType.Account]).Add(Operation.Put);
			((ArrayList)supportedOperations[DocumentType.Account]).Add(Operation.Fetch);
			((ArrayList)supportedOperations[DocumentType.Account]).Add(Operation.Remove);
			((ArrayList)supportedOperations[DocumentType.Undefined]).Add(Operation.GetName);
		}
		
		/// \bried Retrieve the current actor from the server
		protected void GetCurrentActor()
		{
			XmlRpcStruct	results;
			
			Log.Debug("GetCurrentActor()");
			try { results = serverProxy.GetCurrentAccount(); }
				catch (XmlRpcFaultException xmlException)
				{ throw PrepareRPCException("GetCurrentActor", xmlException); }
				catch (XmlRpcTypeMismatchException xmlException)
				{ throw PrepareRPCException("GetCurrentActor", xmlException); }
			currentActor = new Account(results, this);
		}
		
		/// \brief Returns true if the server object can communicate with the server
		public override bool Connected 
		{
			get 
			{
				Log.Debug("Connected == ");
				if (!(currentActor == null))
					return true;
                bool status = true;
				try 
				{
					Log.Debug("Connected == - Getting server time");
					serverProxy.getServerTime();
					Log.Debug("Connected == - Getting current actor");
					GetCurrentActor();
				} catch (Exception ex) 
					{
					Log.Debug("Connected == - An exception has occurred!");
					Log.Debug("Connected == - " + ex.Message);
					Log.Debug("Connected == - " + ex.StackTrace);
					status = false;
					}
				Log.Debug("Connected == " + status.ToString());
				return status;
			}
		}
		
		/// \brief Establish the credentials used to communicate with the server
		public override NetworkCredential Credentials 
		{
			get 
			{
				Log.Debug("Credentials == ");
				return credentials;
			}
			set 
			{
				Log.Debug("Credentials = ");
				if (value == null)
				{
					Log.Debug("Credentials = - Using default credentials from OGoMojo configuration");
					if (rpcClient == null)
						Log.Fatal("rpcClient object is not established!");
					Log.Debug("Username = " + GetDirectiveValue("MOGIRPC2", "Username"));
					Log.Debug("Secret = " + GetDirectiveValue("MOGIRPC2", "Secret"));
					rpcClient.Credentials =
						new NetworkCredential(
							GetDirectiveValue("MOGIRPC2", "Username"),
							GetDirectiveValue("MOGIRPC2", "Secret"));
				} else 
					{
						Log.Debug("Credentials = - Using specified credentials");
						this.credentials = value;
						if (this.rpcClient != null)
							this.rpcClient.Credentials = this.credentials;
					}
			} // End Credentials_set
		}
		
		/// \brief Set the hostname of the server
		public override string Hostname 
		{
			get 
			{
				Log.Debug("Hostname == " + hostname);
				return hostname;
			}
			set 
			{
				if (value == null)
				{
					Log.Debug("Hostname is null, using default host from OGoMojo configuration");
					hostname = GetDirectiveValue("MOGIRPC2", "Hostname");
					Log.Debug("Hostname = " + hostname);
				} else
					{
						hostname = value;
						Log.Debug("Hostname = " + value);
					}
				rpcClient.Url = "http://"  + hostname + "/RPC2";
			}
		}
		
		/// \brief Get the requested document from the server
		/// \note The method returns a generic IDocument that the client application
		/// must explicity cast into the correct type.
		/// \param documentId The document to retrieve from the server
		protected override IMOGIObject ServerGet(string documentId) 
		{
			IDocument result;
			
			Log.Debug("Get(" + documentId + ")");
			result = null;
			switch (MOGI.DetermineType(documentId))
			{
				case DocumentType.Contact:
					result = GetContact((documentId.Split('/'))[2]);
					break;
				case DocumentType.Enterprise:
					result = GetEnterprise((documentId.Split('/'))[2]);
					break;
				case DocumentType.Project:
					result = GetProject((documentId.Split('/'))[2]);
					break;
				case DocumentType.Task:
					result = GetTask((documentId.Split('/'))[2]);
					break;
				case DocumentType.Appointment:
					result = GetAppointment((documentId.Split('/'))[2]);
					break;
				case DocumentType.Account:
					result = GetAccount((documentId.Split('/'))[2]);
					break;
				case DocumentType.Resource:
					result = GetResource((documentId.Split('/'))[2]);
					break;
				case DocumentType.Team:
					result = GetTeam((documentId.Split('/'))[2]);
					break;						
			}
			return result;
		}
		
		/// \brief Remove the requested document from the server
		/// \param documentId The document to remove from the server
		protected override void ServerRemove(string documentId) 
		{
			Log.Debug("Remove(" + documentId + ")");
			switch (MOGI.DetermineType(documentId))
			{
				case DocumentType.Contact:
					RemoveContact((documentId.Split('/'))[2]);
					break;
				case DocumentType.Enterprise:
					RemoveEnterprise((documentId.Split('/'))[2]);
					break;
				case DocumentType.Project:
					RemoveProject((documentId.Split('/'))[2]);
					break;
				case DocumentType.Task:
					RemoveTask((documentId.Split('/'))[2]);
					break;
				case DocumentType.Appointment:
					RemoveAppointment((documentId.Split('/'))[2]);
					break;
				case DocumentType.Account:
					RemoveAccount((documentId.Split('/'))[2]);
					break;
				case DocumentType.Resource:
					RemoveResource((documentId.Split('/'))[2]);
					break;
				case DocumentType.Team:
					RemoveTeam((documentId.Split('/'))[2]);
					break;						
			}
		}		
		
		/// \brief Dispatch a search to the server
		/// \param documentType The type of document to search for
		/// \param keyParts The set of search criteria
		/// \param max The maximum number of objects to return from the search
		/// \note This method is called by the public method Search(string)
		protected override ArrayList ServerSearch(DocumentType documentType, string[] keyPairs, int max) 
		{
			Log.Debug("SearchServer(" + documentType.ToString() + ", ..., " + max.ToString() + ")");
			ArrayList result = null;
			switch (documentType) 
			{
				case DocumentType.Contact:
					result = SearchForContacts(keyPairs, max);
					break;
				case DocumentType.Enterprise:
					result = SearchForEnterprises(keyPairs, max);
					break;
				case DocumentType.Project:
					result = SearchForProjects(keyPairs, max);
					break;
				case DocumentType.Task:
					result = SearchForTasks(keyPairs, max);
					break;
				case DocumentType.Appointment:
					result = SearchForAppointments(keyPairs, max);
					break;
				case DocumentType.Account:
					result = SearchForAccounts(keyPairs, max);
					break;
				case DocumentType.Resource:
					result = SearchForResources(keyPairs, max);
					break;
				case DocumentType.Team:
					result = SearchForTeams(keyPairs, max);
					break;				
			}
			return result;
		}
		
		/// \brief Retrive array of contacts from server based on criteria
		/// \param keyPairs
		/// \param max
		/// \note Called from protected method ServerSearch(...)
		/// \todo Determine supported qualifiers
		protected ArrayList SearchForContacts(string[] keyPairs, int max) 
		{
			Array		rpcResults;
			FetchSpec	fetchSpec;
			ArrayList	results;	
			
			Log.Debug("SearchForContacts(..., " + max.ToString() + ")");
			rpcResults = null;
			results = new ArrayList();
			fetchSpec = new FetchSpec(this, DocumentType.Contact, keyPairs, max);
			rpcResults = serverProxy.ContactFetch(fetchSpec.Wire);
			foreach(XmlRpcStruct rpcStruct in rpcResults) 
			{
				results.Add((IContact)new Contact(rpcStruct, this));
			}
			return results;
		}
		
		/// \brief Retrive array of enterprises from server based on criteria
		/// \param keyPairs
		/// \param max
		/// \note Called from protected method ServerSearch(...)
		/// \todo Determine supported qualifiers
		protected ArrayList SearchForEnterprises(string[] keyPairs, int max) 
		{
			Array		rpcResults;
			FetchSpec	fetchSpec;
			ArrayList	results;	
			
			Log.Debug("SearchForEnterprises(..., " + max.ToString() + ")");
			rpcResults = null;
			results = new ArrayList();
			fetchSpec = new FetchSpec(this, DocumentType.Enterprise, keyPairs, max);
			Log.Debug("SearchForEnterprises(...) - Criteria = " + fetchSpec.Criteria);
			rpcResults = serverProxy.EnterpriseFetch(fetchSpec.Wire);
			foreach(XmlRpcStruct rpcStruct in rpcResults) 
			{
				results.Add((IEnterprise)new Enterprise(rpcStruct, this));
				Log.Debug("SearchForEnterprises(...) - Found " + RPC2.ShortId(rpcStruct["id"].ToString()));
			}
			Log.Debug("SearchForEnterprises(...) - " + results.Count.ToString() + " - enterprises returned");
			return results;	
		}
		
		/// \brief Retrive array of projects from server based on criteria
		/// \param keyPairs
		/// \param max
		/// \note Called from protected method ServerSearch(...)
		/// \todo Implement
		protected ArrayList SearchForProjects(string[] keyPairs, int max) 
		{
			//Array rpcResults;
			//rpcResults = null;
			Log.Debug("SearchForProjects(..., " + max.ToString() + ")");
			lastError = "Search Type Not Implemented";
			return null;
		}
		
		
		/// \brief Retrieves a group of tasks based on criteria
		/// \param keyPairs Criteria to search for
		/// \param max Maximum number of tasks to return
		/// \todo Implement max qualifier
		/// \todo Implement retrieval by project
		/// \note Qualifiers supported: list=[todo|delegated|archived], anything else goes to a fetchSpec
		protected ArrayList SearchForTasks(string[] keyPairs, int max) 
		{
			Array rpcResults;
			ArrayList results;
			
			Log.Debug("SearchForTasks(..., " + max.ToString() + ")");
			results = new ArrayList();
			rpcResults = serverProxy.GetToDoList();
			switch((keyPairs[0].Split('='))[0])
			{
				case "list":
					Log.Debug("SearchForTasks(...) - Get List");
					switch ((keyPairs[0].Split('='))[1])
					{
						case "todo":
							Log.Debug("SearchForTasks(...) - GetToDoList");
							rpcResults = serverProxy.GetToDoList();
							break;
						case "delegated":
							Log.Debug("SearchForTasks(...) - GetDelegatedJobs");
							rpcResults = serverProxy.GetDelegatedJobs();
							break;
						case "archived":
							Log.Debug("SearchForTasks(...) - GetArchivedJobs");
							rpcResults = serverProxy.GetArchivedJobs();
							break;
					}
					break;
				default:
					object		person;
					FetchSpec	fetchSpec;
					
					Log.Debug("SearchForTasks(...) - Get By FetchSpec");
					foreach(string q in keyPairs)
						Log.Debug("SearchForTasks(...) - Q: " + q);
					person = serverProxy.GetContactById(Convert.ToInt32(MOGI.Id(currentActor.Name)));
					if (person is XmlRpcStruct)
					{
						fetchSpec = new FetchSpec(this, DocumentType.Task, keyPairs, max);
						Log.Debug("SearchForTasks(...) - Qualifier=" + fetchSpec.Criteria);
						if (fetchSpec.Valid)
							rpcResults = serverProxy.GetJobs((XmlRpcStruct)person, fetchSpec.Wire);
							else
								throw new InvalidSearchSpecException();
					}
					break;
			}
			Log.Debug("SearchForTasks(...) - Jobs Retrieved: " + rpcResults.Length.ToString());
			foreach(XmlRpcStruct rpcStruct in rpcResults)
				results.Add((ITask)new Task(rpcStruct, this));
			return results;		
		}

		/// \brief Retrive array of appointments from server based on criteria
		/// \param keyPairs
		/// \param max
		/// \note Called from protected method ServerSearch(...)
		/// \note Supported qualifiers are range=[date.date] OR
		/// \todo Requires additional testing for non-range searches
		protected ArrayList SearchForAppointments(string[] keyPairs, int max) 
		{
			ArrayList results;
			FetchSpec fetchSpec;

			Log.Debug("SearchForAppointments(..., " + max.ToString() + ")");
			if ((keyPairs[0].Split('='))[0] == "range")
			{
				Log.Debug("SearchForAppointments(..., " + max.ToString() + ") - RANGE");
				results = GetAppointmentsInRange(
					(keyPairs[0].Split('=')[1]).Split('.')[0],
					(keyPairs[0].Split('=')[1]).Split('.')[1]);
			} else
				{
					results = new ArrayList();
					fetchSpec = new FetchSpec(this, DocumentType.Appointment, keyPairs, max);
					Log.Debug("SearchForAppointments(..., " + max.ToString() + ") - QUALIFIER:" + fetchSpec.Criteria);
					foreach(XmlRpcStruct rpcStruct in serverProxy.AppointmentFetch(fetchSpec.Wire)) 
						results.Add((IAppointment)new Appointment(rpcStruct, this));
				}
			Log.Debug("SearchForAppointments(..., " + max.ToString() + ") - FOUND:" + results.Count.ToString());
			return results;				
		}
		
		/// \brief Retrive array of accounts from server based on criteria
		/// \param keyPairs
		/// \param max
		/// \note Called from protected method ServerSearch(...)		
		/// \todo Test
		protected ArrayList SearchForAccounts(string[] keyPairs, int max) 
		{
			ArrayList	results;
			string		leftSide;
			string		rightSide;

			Log.Debug("SearchForAccounts(..., " + max.ToString() + ")");
			results = new ArrayList();
			try
			{
				leftSide = (keyPairs[0].Split('='))[0];
				rightSide = (keyPairs[0].Split('='))[1];
				Log.Debug("SearchForAccounts(..., " + max.ToString() + ") " + leftSide + ":" + rightSide);
				switch(leftSide)
				{
					case "login":
					case "number":
						// These routines should only return single documents
						object	rpcResult;
						rpcResult = null;
						switch(leftSide)
						{
							case "login":
								rpcResult = serverProxy.GetAccountByLogin(rightSide);
								break;
							case "number":
								rpcResult = serverProxy.GetAccountByNumber(rightSide);
								break;
						}
						if (!(rpcResult == null))
							if(rpcResult is XmlRpcStruct)
								results.Add((IAccount)new Account((XmlRpcStruct)rpcResult, this));
						break;
					// Call routines that may return multiple accounts
					default:
						Array		rpcResults;
						rpcResults = null;
						switch((keyPairs[0].Split('='))[0])
						{
							case "template":
								rpcResults = null;
								break;
							case "all":
								rpcResults = null;
								break;
						}
						if (rpcResults != null)
							foreach(XmlRpcStruct rpcStruct in rpcResults) 
								results.Add((IAccount)new Account(rpcStruct, this));
						break;
				}
				Log.Debug("SearchForAccounts(..., " + max.ToString() + ") - FOUND:" + results.Count.ToString());
			} catch (XmlRpcFaultException xmlException)
					{ throw PrepareRPCException("SearchForAccounts", xmlException); }
					catch (XmlRpcTypeMismatchException xmlException)
					{ 
						if (RPC2.IsEmptyResult(xmlException)) results = null;
							else throw PrepareRPCException("SearchForAccounts", xmlException); 
					}
			return results;				
		}
		
		/// \brief Retrive array of resources from server based on criteria
		/// \param keyPairs
		/// \param max
		/// \note Called from protected method ServerSearch(...)	
		/// \todo Implement
		protected ArrayList SearchForResources(string[] keyPairs, int max) 
		{
			Array rpcResults;
			ArrayList results;
			
			Log.Debug("SearchForResources(..., " + max.ToString() + ")");
			rpcResults = null;
			results = new ArrayList();
			FetchSpec	fSpec;
			fSpec = new FetchSpec(this, DocumentType.Resource, keyPairs, 50);
			if (!(fSpec.Valid))
				throw new InvalidSearchSpecException();
			try { rpcResults = serverProxy.GetResources(fSpec.Wire); }
				catch (XmlRpcFaultException xmlException)
				{ throw PrepareRPCException("SearchForResources", xmlException); }
				catch (XmlRpcTypeMismatchException xmlException)
				{ throw PrepareRPCException("SearchForResources", xmlException); }
			if (!(rpcResults == null))
				foreach(XmlRpcStruct result in rpcResults)
					results.Add((IResource)new Resource(result, this));
			return results;
		}
		
		/// \brief Retrive array of teams from server based on criteria
		/// \param keyPairs
		/// \param max
		/// \note Called from protected method ServerSearch(...)
		/// \note supports search pairs: all=all, login={login}, member={login}, number={number}
		protected ArrayList SearchForTeams(string[] keyPairs, int max) 
		{
			ArrayList	results;
			string		leftSide;
			string		rightSide;

			Log.Debug("SearchForTeams(..., " + max.ToString() + ")");
			results = new ArrayList();
			try
			{
				leftSide = (keyPairs[0].Split('='))[0];
				rightSide = (keyPairs[0].Split('='))[1];
				Log.Debug("SearchForTeams(..., " + max.ToString() + ") " + leftSide + ":" + rightSide);
				switch(leftSide)
				{
					case "login":
					case "number":
						// These routines should only return single documents
						XmlRpcStruct	rpcResult;
						rpcResult = null;
						switch(leftSide)
						{
							case "login":
								rpcResult = serverProxy.GetTeamByLogin(rightSide);
								break;
							case "number":
								rpcResult = serverProxy.GetTeamByNumber(rightSide);
								break;
						}
						if (rpcResult != null)
							results.Add((ITeam)new Team(rpcResult, this));
						break;
					// Call routines that may return multiple teams
					default:
						Array		rpcResults;
						rpcResults = null;
						switch((keyPairs[0].Split('='))[0])
						{
							case "all":
								rpcResults = serverProxy.GetTeams();
								break;
							case "member":
								rpcResults = serverProxy.GetAccountsTeams(rightSide);
								break;
						}
						if (rpcResults != null)
							foreach(XmlRpcStruct rpcStruct in rpcResults)
								results.Add((ITeam)new Team(rpcStruct, this));
						break;
				}
				Log.Debug("SearchForTeams(..., " + max.ToString() + ") - FOUND:" + results.Count.ToString());
				// If we have some results get the team members
				if (results.Count > 0)
				{
					Log.Debug("SearchForTeams(..., " + max.ToString() + ") - Getting members");
					foreach(ITeam team in results)
						((Whitemice.MOGI.RPC2.Team)team).SetMembers(GetTeamMembers(team.Number));
				}
			} catch (XmlRpcFaultException xmlException)
					{ throw PrepareRPCException("SearchForTeams", xmlException); }
					catch (XmlRpcTypeMismatchException xmlException)
					{
						if (RPC2.IsEmptyResult(xmlException)) results = null;
							else throw PrepareRPCException("SearchForTeams", xmlException);
					}
			return results;				
		}

		/// \brief Retrieve and ArrayList of account objects that are members of a team
		/// \param number The team number
		/// \todo Wrap RPC call in exception handler
		protected ArrayList GetTeamMembers(string number)		
		{
			Array		rpcResults;
			ArrayList	members;
			Log.Debug("GetTeamMembers(" + number + ")");
			members = new ArrayList();
			rpcResults = serverProxy.GetTeamMembers(number);
			Log.Debug("GetTeamMembers(" + number + ") - Members: " + rpcResults.Length);
			foreach(XmlRpcStruct member in rpcResults)
			{
				Log.Debug("Member Id is " + member["companyId"].ToString());
				members.Add(this.Get("Document/Account/" + member["companyId"].ToString()));
			}
			return members;
		}
		
		/// \brief Retrieve a contact object via its primary key
		/// \param id Primary key of contact object
		protected IDocument GetContact(string id) 
		{
			object		document;
			IContact	contact;
			
			Log.Debug("Get(" + id + ")");
			contact = null;
			if (id == "0")
			{
				document = NewContact();
			} else
				{
					try	{ document = serverProxy.GetContactById(Convert.ToInt32(id)); }
					catch (XmlRpcFaultException xmlException)
					{ throw PrepareRPCException("GetContactById", xmlException); }
					catch (XmlRpcTypeMismatchException xmlException)
					{ throw PrepareRPCException("GetContactById", xmlException); }
					
				}
			if(document is XmlRpcStruct)
				contact = (IContact)new Contact((XmlRpcStruct)document, this);
			return contact;
		}
		
		/// \brief Retrieve an enterprise object via its primary key
		/// \param id Primary key of enterprise object
		protected IDocument GetEnterprise(string id) 
		{
			object	document;
			IEnterprise		enterprise;
			
			Log.Debug("GetEnterprise(" + id + ")");
			enterprise = null;
			if (id == "0")
			{
				document = NewEnterprise();
			} else
				{			
					try { document = serverProxy.GetEnterpriseById(Convert.ToInt32(id)); }
					catch (XmlRpcFaultException xmlException)
					{ throw PrepareRPCException("GetEnterpriseById", xmlException); }
					catch (XmlRpcTypeMismatchException xmlException)
					{ throw PrepareRPCException("GetEnterpriseById", xmlException); }					
				}
			if(document is XmlRpcStruct)
				enterprise = (IEnterprise)new Enterprise((XmlRpcStruct)document, this);
			return enterprise;
		}
		
		/// \brief Get a project by Id from the server
		/// \todo Implement
		protected IDocument GetProject(string id) 
		{
			Log.Debug("GetProject(" + id + ")");
			return null;
		}

		/// \brief Get a task by Id from the server
		/// \param id Primary key id of task document, 0 for a new task document
		protected IDocument GetTask(string id) 
		{	
			XmlRpcStruct	document;
			
			Log.Debug("GetTask(" + id + ")");
			if (id == "0")
				document = NewTask();
				else
					try { document = (XmlRpcStruct)serverProxy.GetJobsById(new string[] { id }).GetValue(0); }	
						catch (XmlRpcFaultException xmlException)
						{ throw PrepareRPCException("GetTask", xmlException); }
						catch (XmlRpcTypeMismatchException xmlException)
						{ throw PrepareRPCException("GetTask", xmlException); }		
			return (ITask)new Task(document, this);
		}
		
		/// \brief Get an appointment by Id from the server
		protected IDocument GetAppointment(string id) 
		{
			object	document;
			IAppointment	appointment;
		
			Log.Debug("GetAppointment(" + id + ")");
			appointment = null;
			if (id == "0")
			{
				document = NewAppointment();
			} else
				{			
					try { document = serverProxy.GetAppointmentById(id); }
						catch (XmlRpcFaultException xmlException)
						{ throw PrepareRPCException("GetAppointmentById", xmlException); }
						catch (XmlRpcTypeMismatchException xmlException)
						{ throw PrepareRPCException("GetAppointmentById", xmlException); }					
				}
			if (document is XmlRpcStruct)
				appointment = (IAppointment)new Appointment((XmlRpcStruct)document, this);			
			return appointment;
		}
		
		/// \brief Get list of appointments between two dates
		/// \param startDate
		/// \param endDate
		/// \note This method is called by SearchForAppointments when a range qualifier is used
		protected ArrayList GetAppointmentsInRange(string startDate, string endDate)
		{
			XmlRpcStruct[]	results;
			ArrayList		appointments;
		
			Log.Debug("GetAppointmentsInRange(" + startDate + ", " + endDate + ")");
			appointments = new ArrayList();
			try { results = serverProxy.GetOverview(
					Convert.ToDateTime(startDate),
					Convert.ToDateTime(endDate)); }
						catch (XmlRpcFaultException xmlException)
						{ throw PrepareRPCException("GetAppointmentById", xmlException); }
						catch (XmlRpcTypeMismatchException xmlException)
						{ throw PrepareRPCException("GetAppointmentById", xmlException); }
			foreach(XmlRpcStruct result in results)
			{
				appointments.Add((IAppointment)new Appointment(result, this));
			}
			return appointments;
		}
		
		/// \brief Get an account by Id from the server
		/// \param	id	Numeric account id
		/// \note Requires NumberPrefix to be defined in the MOGIRPC2 stanza of the configuration
		protected IDocument GetAccount(string id) 
		{
			object	rpcResult;
			Log.Debug("GetAccount(" + id + ")");
			if (id == "0")
			{
				return (IAccount)new Account(NewAccount(), this);
			} else
				try { rpcResult = serverProxy.GetAccountByNumber(GetDirectiveValue("MOGIRPC2", "NumberPrefix") + id); }
					catch (XmlRpcFaultException xmlException)
					{ throw PrepareRPCException("GetAccountByNumber", xmlException); }
					catch (XmlRpcTypeMismatchException xmlException)
					{ throw PrepareRPCException("GetAccountByNumber", xmlException); }
			if (rpcResult is XmlRpcStruct)
				return (IAccount)new Account((XmlRpcStruct)rpcResult, this);
			return null;
		}
		
		/// \brief Get a resource by Id from the server
		/// \todo Implement		
		protected IDocument GetResource(string id) 
		{
			object	rpcResult;
			
			Log.Debug("GetResource(" + id + ")");
			rpcResult = null;
			if (id == "0")
			{
				rpcResult = NewResource();
				Log.Debug("GetResource(" + id + ") - Returning new resource object");
				return (IResource)new Resource((XmlRpcStruct)rpcResult, this);
			} else
				try { rpcResult = serverProxy.GetResourceById(id); }
					catch (XmlRpcFaultException xmlException)
					{ throw PrepareRPCException("GetResource", xmlException); }
					catch (XmlRpcTypeMismatchException xmlException)
					{ throw PrepareRPCException("GetResource", xmlException); }							
			if(rpcResult is XmlRpcStruct)
				return (IResource) new Resource((XmlRpcStruct)rpcResult, this);
			return null;
		}
		
		/// \brief Get a resource by Id from the server
		/// \param Id Primary key id of team to retrieve	
		protected IDocument GetTeam(string id) 
		{
			XmlRpcStruct rpcResult;
			string number;
			ITeam	result;
			
			if (id == "0")
				return (ITeam)new Team(NewTeam(), this);
				else
				{
					rpcResult = null;
					Log.Debug("GetTeam(" + id + ")");
					if (MOGI.IsBuiltIn(id))
					{
						Log.Debug("GetTeam(" + id + ") - Team is a builtin");
						number = "LS" + id;
					} else number = GetDirectiveValue("MOGIRPC2", "NumberPrefix") + id;	
					Log.Debug("GetTeam(" + id + ") - Number is " + number);
					try { rpcResult = serverProxy.GetTeamByNumber(number); }
						catch (XmlRpcFaultException xmlException)
						{ throw PrepareRPCException("GetTeamByNumber", xmlException); }
						catch (XmlRpcTypeMismatchException xmlException)
						{ throw PrepareRPCException("GetTeamByNumber", xmlException); }
					Log.Debug("GetTeam(" + id + ") - Team Retrieved");
					if (rpcResult != null)
					{
						result = (ITeam)new Team(rpcResult, this);
						((Whitemice.MOGI.RPC2.Team)result).SetMembers(GetTeamMembers(result.Number));
					} else
						result = null;
				}
			return result;
		}
		
		/// \brief Create a new empty task document
		/// \todo Test
		protected XmlRpcStruct NewTask()
		{
			XmlRpcStruct document;
			
			Log.Debug("NewTask()");
			document = new XmlRpcStruct();
			document.Add("name", "");
			return document;
		}
		
		/// \brief Create a new empty team document
		/// \todo Test
		protected XmlRpcStruct NewTeam()
		{
			XmlRpcStruct document;
			
			Log.Debug("NewTeam()");
			document = new XmlRpcStruct();
			document.Add("email", "");
			document.Add("description", "");
			document.Add("isLocationTeam", "0");
			document.Add("number", "0");
			document.Add("id", null);
			return document;
		}
		
		/// \bried Construct a new blank resource object
		protected XmlRpcStruct NewResource()
		{
			XmlRpcStruct document;
			
			Log.Debug("NewResource()");
			document = new XmlRpcStruct();
			document.Add("name", "");
			document.Add("email", "");
			document.Add("emailSubject", "");
			document.Add("category", "");
			document.Add("objectVersion", 0);
			return document;
		}
		
		/// \brief Construct a sting[] of Ids from a team object
		/// \param team A team object
		/// \note This is used in Put(team) as the RPC call requires a string[] to set membership
		protected string[] MembersArray(Team team)
		{
			string[] 	members;
			int 		count;
			
			Log.Debug("MembersArray(" + team.Name + ")");
			count = 0;
			members = new string[team.Members.Count];
			foreach(IAccount account in team.Members)
			{
				Log.Debug("MembersArray(" + team.Name + ") - Member " + count.ToString() + " is " + account.Name);
				members[count] = MOGI.Id(account.Name);
				count++;
			}
			Log.Debug("MembersArray(" + team.Name + ") - Count: " + members.Length.ToString());
			return members;
		}
		
		/// \brief Create a new empty account document
		protected XmlRpcStruct NewAccount()
		{
			XmlRpcStruct document;
			
			document = new XmlRpcStruct();
			document.Add("login", "");
			return document;
		}
		
		/// \brief Create a new empty appointment document
		/// \todo Test		
		protected XmlRpcStruct NewAppointment()
		{
			XmlRpcStruct appointment;
			
			appointment = new XmlRpcStruct();
			appointment.Add("startDate", DateTime.Now);
			appointment.Add("endDate", DateTime.Now.AddHours(1));
			appointment.Add("keywords", "");
			appointment.Add("title", "");
			appointment.Add("comment", "");
			appointment.Add("location", "");
			appointment.Add("cycleEndDate", DateTime.Now.AddDays(30));
			appointment.Add("aptType", "meeting");
			return appointment;
		}
		
		/// \brief Returns a structured but blank XmlRpcStruct that can be loaded into an Enterprise object
		protected XmlRpcStruct NewEnterprise()
		{
			Log.Debug("NewEnterprise()");
			return AssembleNewCompanyObject(DocumentType.Enterprise);
		}
		
		/// \brief Returns a structured but blank XmlRpcStruct that can be loaded into an Contact object
		protected XmlRpcStruct NewContact()
		{
			Log.Debug("NewContact()");
			return AssembleNewCompanyObject(DocumentType.Contact);
		}
		
		/// \brief Creates a new empty address document
		internal XmlRpcStruct AssembleNewAddress(string type)
		{
			Log.Debug("AssembleNewAddress(" + type + ")");
			XmlRpcStruct address = new XmlRpcStruct();
			foreach(string element in addressElements) address.Add(element, "");
			address.Add("type", type);
			return address;
		}
		
		/// \brief Creates a new empty phone number document
		internal XmlRpcStruct AssembleNewPhone(string type)
		{
			Log.Debug("AssembleNewPhone(" + type + ")");
			XmlRpcStruct phone = new XmlRpcStruct();
			foreach(string element in phoneElements) phone.Add(element, "");
			phone.Add("type", type);
			return phone;
		}		
		
		/// \brief Creates a new empty Contact or Enterprise document
		/// \param documentType
		protected XmlRpcStruct AssembleNewCompanyObject(DocumentType documentType)
		{
			Log.Debug("AssembleNewCompanyObject(" + documentType.ToString() + ")");
			XmlRpcStruct addresses;
			XmlRpcStruct phones;
			XmlRpcStruct attributes;
			string[] attributeList;
			int attributeCounter;
			
			addresses = new XmlRpcStruct();
			foreach(string addressType in AddressTypes(documentType))
			{
				addresses.Add(addressType, AssembleNewAddress(addressType));
			}
			phones = new XmlRpcStruct();
			foreach(string phoneType in PhoneTypes(documentType))
			{
				phones.Add(phoneType, AssembleNewPhone(phoneType));
			}
			attributes = new XmlRpcStruct();
			attributeList = new string[ExtendedAttributes(documentType).Count];
			attributeCounter = 0;
			foreach(string attribute in ExtendedAttributes(documentType))
			{
				attributes.Add(attribute, "");
				attributeList[attributeCounter] = attribute;
				attributeCounter++;
			}
			XmlRpcStruct document = new XmlRpcStruct();
			document.Add("url", "");
			document.Add("objectVersion", 0);
			document.Add("name", "");			
			document.Add("phones", addresses);
			document.Add("addresses", phones);
			document.Add("extendedAttrs", attributes);
			document.Add("extendedKeys", attributeList);
			if (documentType == DocumentType.Enterprise)
			{
				document.Add("bank", "");
				document.Add("bankCode", "");
				document.Add("email", "");
				document.Add("keywords", "");
			} else
				{
					document.Add("firstname", "");
					///document.Add("isAccount", false);
					///document.Add("isPrivate", false);
					document.Add("login", "");
					document.Add("degree", "");
					document.Add("nickname", "");
				}
			return document;
		}
		
		/// \brief Puts a document to the server
		/// \param IDocument The document to store on the server
		/// \note Depending upon the document type of the document provided
		/// this method will either call a document type specific Put method
		/// such as PutEnterprise or it will throw a NoTypeException if the
		/// document type is undefined.
		protected override void ServerPut(IDocument document) 
		{
			Log.Debug("Put(" + document.Name + ")");
			switch (document.Type)
			{
				case DocumentType.Contact:
					PutContact((Contact)document);
					break;
				case DocumentType.Enterprise:
					PutEnterprise((Enterprise)document);
					break;
				case DocumentType.Project:
					PutProject((Project)document);
					break;
				case DocumentType.Task:
					PutTask((Task)document);
					break;
				case DocumentType.Appointment:
					PutAppointment((Appointment)document);
					break;
				case DocumentType.Account:
					PutAccount((Account)document);
					break;
				case DocumentType.Team:
					PutTeam((Team)document);
					break;
				case DocumentType.Resource:
					PutResource((Resource)document);
					break;
				case DocumentType.Undefined:
					throw new NoTypeException("Attempting to put to server a document with and unknown type");
			}
		}

		/// \brief Push an contact object to the server
		/// \param document The object to push to the server
		/// \note if the document.Number is 0 then this is treated as a
		/// create request, otherwise it operates an update request.		
		protected void PutContact(Contact document) 
		{
			int newId;
		
			Log.Debug("PutContact(" + document.Name + ")");
			if (document.Number == "0")
			{
				try	{ newId = serverProxy.CreateContact(document.document); }
					catch (XmlRpcFaultException xmlException)
					{ throw PrepareRPCException("CreateContact", xmlException); }
					catch (XmlRpcTypeMismatchException xmlException)
					{ throw PrepareRPCException("CreateContact", xmlException); }
				try { document.document = (XmlRpcStruct)serverProxy.GetContactById(newId); }
					catch (XmlRpcFaultException xmlException)
					{ throw PrepareRPCException("GetContactById", xmlException); }
					catch (XmlRpcTypeMismatchException xmlException)
					{ throw PrepareRPCException("GetContactById", xmlException); }
				
			} else
				{
					try { serverProxy.UpdateContact(document.document); }
						catch (XmlRpcFaultException xmlException)
						{ throw PrepareRPCException("UpdateContact", xmlException); }
						catch (XmlRpcTypeMismatchException xmlException)
						{ throw PrepareRPCException("UpdateContact", xmlException); }
				}
		}

		/// \brief Push an enterprise object to the server
		/// \param document The object to push to the server
		/// \note if the document.Number is 0 then this is treated as a
		/// create request, otherwise it operates an update request.
		protected void PutEnterprise(Enterprise document)
		{
			int newId;
		
			Log.Debug("PutEnterprise(" + document.Name + ")");
			if (document.Number == "0")
			{
				try	 { newId = serverProxy.CreateEnterprise(document.document); }
					catch (XmlRpcFaultException xmlException)
					{ throw PrepareRPCException("CreateEnterprise", xmlException); }
					catch (XmlRpcTypeMismatchException xmlException)
					{ throw PrepareRPCException("CreateEnterprise", xmlException); }					
				try { document.document = (XmlRpcStruct)serverProxy.GetEnterpriseById(newId);	}
					catch (XmlRpcFaultException xmlException)
					{ throw PrepareRPCException("GetEnterpriseById", xmlException); }
					catch (XmlRpcTypeMismatchException xmlException)
					{ throw PrepareRPCException("GetEnterpriseById", xmlException); }	
			} else 
				{
				try { document.document = (XmlRpcStruct)serverProxy.UpdateEnterprise(document.document); }
					catch (XmlRpcFaultException xmlException)
					{ throw PrepareRPCException("UpdateEnterprise", xmlException); }
					catch (XmlRpcTypeMismatchException xmlException)
					{ throw PrepareRPCException("UpdateEnterprise", xmlException); }	
				}
		}
		
		/// \brief Add a relationship whose source is an enterprise object
		protected override void PutEnterpriseRelation(IRelation relation)
		{
			switch(relation.Type)
			{
				case ERelationType.Member:
					try { serverProxy.AssociateContactWithEnterprise(MOGI.Id(relation.Source), MOGI.Id(relation.Target)); }
						catch (XmlRpcFaultException xmlException)
						{ throw PrepareRPCException("AssociateContactWithEnterprise", xmlException); }
						catch (XmlRpcTypeMismatchException xmlException)
						{ throw PrepareRPCException("AssociateContactWithEnterprise", xmlException); }						
					break;
				default:
					throw new NotSupportedException("Backend does not creating relations on this document type");
			}
		}
		
		/// \brief Add a relationship whose source is a contact object
		/// \note Supporting memberOf/enterprise
		/// \param relation An IRelation object
		protected override void PutContactRelation(IRelation relation)
		{
			Log.Debug("PutContactRelation(" + relation.Source + " -> "  + relation.Target + ")");
			switch(relation.Type)
			{
				case ERelationType.MemberOf:
					Log.Debug("PutContactRelation(...) - RelationType is MemberOf");
					switch (MOGI.DetermineType(relation.Target))
					{
						case DocumentType.Enterprise:
							Log.Debug("PutContactRelation(...) - Add contact to enterprise as member");
							try { serverProxy.AssociateContactWithEnterprise(MOGI.Id(relation.Target), MOGI.Id(relation.Source)); }
								catch (XmlRpcFaultException xmlException)
								{ throw PrepareRPCException("AssociateContactWithEnterprise", xmlException); }
								catch (XmlRpcTypeMismatchException xmlException)
								{ throw PrepareRPCException("AssociateContactWithEnterprise", xmlException); }												
							break;
						default:
							throw new NotSupportedException("Backend does not creating relations on this document type");
					}
					break;
				default:
					throw new NotSupportedException("Backend does not creating relations on this document type");
			}
		}		
		
		/// \brief Store a project document to the server
		/// \todo Implement
		protected void PutProject(Project document)
		{
			Log.Debug("PutProject(" + document.Name + ") : Not Implemented");
		}
		
		/// \brief Store a resource document to the server
		/// \todo Implement
		protected void PutResource(Resource document)
		{
			Log.Debug("PutResource(" + document.Name + ")");
			if (MOGI.Id(document.Name) == "0")
			{
				string documentId;
				Log.Debug("PutResource(...) - Putting new resource object");
				try 
				{ 
					documentId = RPC2.ShortId(serverProxy.CreateResource(document.document));
					document.document = (XmlRpcStruct)serverProxy.GetResourceById(documentId);
				} catch (XmlRpcFaultException xmlException)
					{ throw PrepareRPCException("CreateResource", xmlException); }
					catch (XmlRpcTypeMismatchException xmlException)
					{ throw PrepareRPCException("CreateResource", xmlException); }
			} else
				{
					try { document.document = serverProxy.UpdateResource(document.document); }
						catch (XmlRpcFaultException xmlException)
						{ throw PrepareRPCException("UpdateResource", xmlException); }
						catch (XmlRpcTypeMismatchException xmlException)
						{ throw PrepareRPCException("UpdateResource", xmlException); }
				}
			Log.Debug("PutResource(...) - Resource id is " + (string)document.document["id"]);
		}

		/// \brief Store a task document to the server
		/// \param document Task document
		/// \note This routine contains a hack to work around Bug#1701
		/// \warn Several OGo bugs affect being able to put tasks to the server: 1701, 1702, 239
		protected void PutTask(Task document)
		{
			Contact person;
		
			Log.Debug("PutTask(" + document.Name + ")");
			person = (Contact)this.Get("Document/Contact/" + MOGI.Id(currentActor.Name));
			if (person == null) Log.Debug("PutTask(...) - Current actor person is *NULL*");
			if (document.document.Contains("kilometers"))
				document.document.Remove("kilometers");
			if (MOGI.Id(document.Name) == "0")
			{
				Log.Debug("PutTask(" + document.Name + ") - Create Task");
				try { document.document = serverProxy.CreateJob(person.document, document.document); }
					catch (XmlRpcFaultException xmlException)
					{ throw PrepareRPCException("CreateJob", xmlException); }
					catch (XmlRpcTypeMismatchException xmlException)
					{ throw PrepareRPCException("CreateJob", xmlException);	}
			} else
				{
					Log.Debug("PutTask(" + document.Name + ") - Update Task");
					try { document.document = serverProxy.UpdateJob(person.document, document.document); }
						catch (XmlRpcFaultException xmlException)
						{ throw PrepareRPCException("UpdateJob", xmlException); }
						catch (XmlRpcTypeMismatchException xmlException)
						{ throw PrepareRPCException("UpdateJob", xmlException);	}
				}
		}
		
		/// \brief Store an appointment document to the server
		/// \todo Requires testsing		
		protected void PutAppointment(Appointment document)
		{
			string[]	participants;
			
			Log.Debug("PutAppointment(" + document.Name + ")");
			try 
			{
				if (MOGI.Id(document.Name) == "0")
				{
					Log.Debug("PutAppointment(" + document.Name + ") : appointment.insert");
					document.document = serverProxy.CreateAppointment(document.document);
				} else
					{
						Log.Debug("PutAppointment(" + document.Name + ") : appointment.update");
						document.document = serverProxy.UpdateAppointment(document.document);
					}
			} catch (XmlRpcFaultException xmlException)
				{ throw PrepareRPCException("PutAppointment", xmlException); }
				catch (XmlRpcTypeMismatchException xmlException)
				{ throw PrepareRPCException("PutAppointment", xmlException); }
			if (document.Participants.Count > 0)
			{
				participants = new string[document.Participants.Count];
				for (int i = 0; i < participants.Length; i++)
					participants[i] = MOGI.Id(document.Participants[i].ToString());
				try { 
					document.document = serverProxy.SetParticipants(MOGI.Id(document.Name), participants);
					document.UpdateParticipants();
					} catch (XmlRpcFaultException xmlException)
						{ throw PrepareRPCException("PutAppointment", xmlException); }
						catch (XmlRpcTypeMismatchException xmlException)
						{ throw PrepareRPCException("PutAppointment", xmlException); }					
			}
		}
		
		/// \brief Store an account document to the server
		/// \warn The OGo XML-RPC lacks a team.update method!  Update only sets members
		/// \todo Implement		
		protected void PutAccount(Account document)
		{
			Log.Debug("PutAccount(" + document.Name + ") : Not Implemented");
			try
			{
				if (MOGI.Id(document.Name) == "0")
					document.document = serverProxy.CreateAccount(document.document);
					else document.document = serverProxy.UpdateAccount(document.document);
			} catch (XmlRpcFaultException xmlException)
				{ throw PrepareRPCException("PutAccount", xmlException); }
				catch (XmlRpcTypeMismatchException xmlException)
				{ throw PrepareRPCException("PutAccount", xmlException); }
		}
		
		/// \brief Store a team document to the server
		/// \todo Test putting a new team, putting a team retrieved from the server seems to work.	
		protected void PutTeam(Team document)
		{
			Log.Debug("PutTeam(" + document.Name + ")");
		
			if (document.Number == "0")
			{
				try	 
				{
					Log.Debug("PutTeam(" + document.Name + ") - Create");
					document.document = serverProxy.CreateTeam(document.document);
					Log.Debug("PutTeam(" + document.Name + ") - Create, Setting membership");
					serverProxy.SetTeamMembership(document.Number, MembersArray(document));
					Log.Debug("PutTeam(" + document.Name + ") - Create, Fetching membership");
					document.SetMembers(GetTeamMembers(document.Number));
				}
					catch (XmlRpcFaultException xmlException)
					{ throw PrepareRPCException("CreateTeam", xmlException); }
					catch (XmlRpcTypeMismatchException xmlException)
					{ throw PrepareRPCException("CreateTeam", xmlException); }					
			} else 
				{
					try 
					{
						Log.Debug("PutTeam(" + document.Name + ") - Update");
						Log.Debug("PutTeam(" + document.Name + ") - Update, Setting membership");
						serverProxy.SetTeamMembership(MOGI.Id(document.Name), MembersArray(document));
						Log.Debug("PutTeam(" + document.Name + ") - Update, Fetching membership");
						document.SetMembers(GetTeamMembers(document.Number));
					}
						catch (XmlRpcFaultException xmlException)
						{ throw PrepareRPCException("UpdateTeam", xmlException); }
						catch (XmlRpcTypeMismatchException xmlException)
						{ throw PrepareRPCException("UpdateTeam", xmlException); }	
				}
			Log.Debug("PutTeam(" + document.Name + ") - Complete");
		}
		
		/// \brief Remove specified contact object from server
		/// \params id Primary key of object
		/// \note If id == 0 this method id a noop
		protected void RemoveContact(string id)
		{
			Log.Debug("RemoveContact(" + id + ")");
			if (id != "0") serverProxy.DeleteContact(id);
		}
		
		/// \brief Removes the specified enterprise from the server
		/// \param id Id of object to remove
		/// \note I would prefer to perform this operation using RemoveEnterprise(id) but
		/// that method is not currently working - so I've used RemoveEnterpriseByNumber
		/// which requires the prefix/label used on object numbers,  this value should
		/// be set as the NumberPrefix directive in the MOGIRPC2 stanza.
		protected void RemoveEnterprise(string id)
		{
			Log.Debug("RemoveEnterprise(" + id + ")");
			if (id != "0") 
			{
				try { serverProxy.RemoveEnterprise(Convert.ToInt32(id)); 
					}
					catch (XmlRpcFaultException xmlException)
					{ throw PrepareRPCException("RemoveEnterprise", xmlException); }
					catch (XmlRpcTypeMismatchException xmlException)
					{ throw PrepareRPCException("RemoveEnterprise", xmlException);	}
			}
		}
		
		/// \brief Remove specified project object from server
		/// \params id Primary key of object
		/// \note If id == 0 this method is a no-op
		protected void RemoveProject(string id)
		{
			Log.Debug("RemoveProject(" + id + ") : Not Implemented");
		}

		/// \brief Remove specified task object from server
		/// \params id Primary key of object
		/// \note If id == 0 this method is a no-op		
		protected void RemoveTask(string id)
		{
			object person;
		
			Log.Debug("RemoveTask(" + id + ")");
			if (id == "0")
				return;
			try
			{
				person = serverProxy.GetContactById(Convert.ToInt32(MOGI.Id(currentActor.Name)));
				if (person is XmlRpcStruct)
					serverProxy.DeleteJob((XmlRpcStruct)person, id);
			} catch (XmlRpcFaultException xmlException)
				{ throw PrepareRPCException("RemoveTask", xmlException); }
				catch (XmlRpcTypeMismatchException xmlException)
				{ throw PrepareRPCException("RemoveTask", xmlException);	}
		}
		
		/// \brief Remove specified appointment object from server
		/// \params id Primary key of object
		/// \note If id == 0 this method is a no-op		
		protected void RemoveAppointment(string id)
		{
			Log.Debug("RemoveAppointment(" + id + ")");
			try { serverProxy.DeleteAppointment(id); }
				catch (XmlRpcFaultException xmlException)
				{ throw PrepareRPCException("RemoveEnterprise", xmlException); }
				catch (XmlRpcTypeMismatchException xmlException)
				{ throw PrepareRPCException("RemoveEnterprise", xmlException);	}			
		}

		/// \brief Remove specified account object from server
		/// \params id Primary key of object
		/// \note If id == 0 this method is a no-op		
		protected void RemoveAccount(string id)
		{
			Log.Debug("RemoveAccount(" + id + ")");
			if (id != "0")
				try
				{
					serverProxy.DeleteAccountByNumber(
						GetDirectiveValue("MOGIRPC2", "NumberPrefix") + id);
				} catch (XmlRpcFaultException xmlException)
					{ throw PrepareRPCException("RemoveAccount", xmlException); }
					catch (XmlRpcTypeMismatchException xmlException)
					{ throw PrepareRPCException("RemoveAccount", xmlException);	}	
		}

		/// \brief Remove specified resource object from server
		/// \params id Primary key of object
		/// \note If id == 0 this method is a no-op		
		protected void RemoveResource(string id)
		{
			Log.Debug("RemoveResource(" + id + ")");
			if (!(id == "0"))
				try { serverProxy.DeleteResource(id); }
					catch (XmlRpcFaultException xmlException)
					{ throw PrepareRPCException("RemoveResource", xmlException); }
					catch (XmlRpcTypeMismatchException xmlException)
					{ throw PrepareRPCException("RemoveResource", xmlException);	}				
		}

		/// \brief Remove specified team object from server
		/// \params id Primary key of object
		/// \note If id == 0 this method is a no-op		
		protected void RemoveTeam(string id)
		{
			Log.Debug("RemoveTeam(" + id + ")");
			throw new Whitemice.MOGI.NotSupportedException("Removal of team objects not supported by backend");
		}
		
		/// \brief Place an XmlRpcFaultException inside an RPCException
		/// \param message
		/// \param xmlException
		/// \note This is so clients only have to handle one type of exception
		protected RPCException PrepareRPCException(string message, XmlRpcFaultException xmlException)
		{
			Log.Debug("PrepareRPCException(" + message + ", XmlRpcFaultException...)");
			RPCException rpcException = new RPCException("An RPC Error Occured Processing " +  message, xmlException);
			rpcException.RPCCode = xmlException.FaultCode;
			rpcException.RPCMessage = xmlException.FaultString;

			///this.Log.LogException(rpcException);
			return rpcException;
		}

		/// \brief Place an XmlRpcTypeMismatchException inside an RPCException
		/// \param message
		/// \param xmlException
		/// \note This is so clients only have to handle one type of exception		
		protected RPCException PrepareRPCException(string message, XmlRpcTypeMismatchException xmlException)
		{
			Log.Debug("PrepareRPCException(" + message + ", XmlRpcTypeMismatchException...)");
			RPCException rpcException = new RPCException("An RPC Encoding Error Occured Processing " +  message, xmlException);
			rpcException.RPCCode = 0;
			rpcException.RPCMessage = xmlException.Message;
			///this.EventLog.LogException(rpcException);
			return rpcException;
		}
		
		/// \brief Determine if the current actor has write access to the specified document
		/// \param documentId
		protected override bool ServerAllowsWrite(string documentId)
		{
			bool permitted;
			
			Log.Debug("ServerAllowsWrite(" + documentId + ")");
			permitted = false;
			try { permitted = serverProxy.OperationAllowed("w", MOGI.Id(documentId), MOGI.Id(currentActor.Name)); }
				catch (XmlRpcFaultException xmlException)
					{ throw PrepareRPCException("RPC Encoding Error Checking Write Access", xmlException); }
				catch (XmlRpcTypeMismatchException xmlException)
					{ throw PrepareRPCException("RPC Type Mismatch Checking Write Accss", xmlException); }
			return permitted;
		}
		
		/// \brief Determine if the current actor has read access to the specified document
		/// \param documentId
		protected override bool ServerAllowsRead(string documentId)
		{
			bool permitted;
			
			Log.Debug("ServerAllowsRead(" + documentId + ")");
			permitted = false;
			try { permitted = serverProxy.OperationAllowed("r", MOGI.Id(documentId), MOGI.Id(currentActor.Name)); }
				catch (XmlRpcFaultException xmlException)
					{ throw PrepareRPCException("RPC Encoding Error Checking Read Access", xmlException); }
				catch (XmlRpcTypeMismatchException xmlException)
					{ throw PrepareRPCException("RPC Type Mismatch Checking Read Accss", xmlException);	}
			return permitted;
		}
		
		/// \brief Set an access rule on the server
		/// \param documentId The object to apply the permissions to
		/// \param right Rights structure to impose on document
		protected override void ServerSetRight(string documentId, Rights right)
		{
			string operations;
			
			Log.Debug("ServerSetRight(" + documentId + ",...)");
			operations = "";
			if (right.Read) operations = operations + "r";
			if (right.Write) operations = operations + "w";
			Log.Debug("ServerSetRight(" + documentId + ",...) : operations = " + operations);
			try { serverProxy.SetOperationsAllowed(operations, MOGI.Id(documentId), MOGI.Id(right.Holder)); }
				catch (XmlRpcFaultException xmlException)
					{ throw PrepareRPCException("RPC Encoding Error Checking Read Access", xmlException); }
				catch (XmlRpcTypeMismatchException xmlException)
					{ throw PrepareRPCException("RPC Type Mismatch Checking Read Accss", xmlException);	 }
		}
		
		/// \brief Return the defined resource categories
		/// \param criteria A regular expression to limit categories returned
		/// \todo Implement limiting return of categories
		protected override ArrayList GetResourceCategories(string criteria)
		{
			ArrayList 	results;
			string[]	rpcResults;
			
			Log.Debug("GetResourceCategories(" + criteria + ")");
			rpcResults = null;
			results = new ArrayList();
			try { rpcResults = serverProxy.GetResourceCategories(); }
				catch (XmlRpcFaultException xmlException)
					{ throw PrepareRPCException("RPC encoding error retrieving resource categories", xmlException); }
				catch (XmlRpcTypeMismatchException xmlException)
					{ throw PrepareRPCException("RPC type mismatch retrieving resource categories", xmlException);	 }
			if (!(rpcResults == null))
				foreach(string category in rpcResults)
					results.Add(category);
			return results;
		}
		
		/// \brief Unimplemented method to retrieve task annotations from the server		
		protected override ArrayList GetTaskAnnotations(string documentId)
		{
			ArrayList		results;
			Array			rpcResults;
			
			Log.Debug("GetTaskAnnotations(document=" + documentId + ")");
			results = new ArrayList();
			try { rpcResults = serverProxy.GetJobHistory(MOGI.Id(documentId)); }
				catch (XmlRpcFaultException xmlException)
					{ throw PrepareRPCException("RPC encoding error retrieving task annotations", xmlException); }
				catch (XmlRpcTypeMismatchException xmlException)
					{ throw PrepareRPCException("RPC type mismatch retrieving task annotations", xmlException);	 }			
			foreach(XmlRpcStruct result in rpcResults)
				results.Add((ITaskAnnotation) new TaskAnnotation(result, this));
			Log.Debug("GetTaskAnnotations(document=" + documentId + ") - " + results.Count.ToString() + " annotations");
			return results;
		}
		
		protected override void PutTaskAnnotation(ITaskAnnotation document)
		{
			Log.Debug("MakeTaskAnnotation(documentId=" + MOGI.Id(document.Document) + ")");	
			try
			{
				switch(document.Action)
				{
					case ETaskStatus.Accepted:
						Log.Debug("MakeTaskAnnotation(document=" + document.Document + ") - Accepted");
						serverProxy.AcceptJob(MOGI.Id(document.Document), document.Comment);
						break;
					case ETaskStatus.Rejected:
						Log.Debug("MakeTaskAnnotation(document=" + document.Document + ") - Rejected");
						serverProxy.RejectJob(MOGI.Id(document.Document), document.Comment);
						break;
					case ETaskStatus.Done:
						Log.Debug("MakeTaskAnnotation(document=" + document.Document + ") - Done");
						serverProxy.MarkJobDone(MOGI.Id(document.Document), document.Comment);
						break;
					case ETaskStatus.Archived:
						Log.Debug("MakeTaskAnnotation(document=" + document.Document + ") - Archived");
						serverProxy.ArchiveJob(MOGI.Id(document.Document), document.Comment);
						break;
					default:
						Log.Debug("MakeTaskAnnotation(document=" + document.Document + ") - Default");
						serverProxy.AnnotateJob(MOGI.Id(document.Document), document.Comment);
						break;
				}
			} catch (XmlRpcFaultException xmlException)
				{ throw PrepareRPCException("RPC encoding error posting task annotations", xmlException); }
				catch (XmlRpcTypeMismatchException xmlException)
				{ throw PrepareRPCException("RPC type mismatch posting task annotations", xmlException);	 } 
		}
		
		protected override IAnnotation TaskAnnotation(string documentId)
		{
			Log.Debug("TaskAnnotation(" + documentId + ")");
			return (IAnnotation)new TaskAnnotation(documentId, this);
		}
		
		/// \brief Return ArrayList of IRelation objects
		/// \param documentId
		/// \note Currently returns member relations for persons members of the enterprise
		protected override ArrayList GetEnterpriseRelations(string documentId)
		{
			ArrayList	results;
			object		rpcResults;
			
			Log.Debug("GetEnterpriseRelations(" + documentId + ")");
			results = new ArrayList();
			rpcResults = serverProxy.GetEnterpriseContacts(MOGI.Id(documentId));
			if (rpcResults is Array)
				if (((Array)rpcResults).Length > 0)
					foreach(XmlRpcStruct result in (Array)rpcResults)
					{
						string memberId;
						
						memberId = "Document/Contact/" + RPC2.ShortId(result["id"].ToString());
						Log.Debug("GetEnterpriseRelations(...) - Member=" + memberId);
						IRelation relation = (IRelation) new Relation(
																ERelationType.Member,
																documentId,
																memberId,
																null, null);
						Log.Debug("GetEnterpriseRelations(...) - Relation:" + relation.Name);
						results.Add(relation);
					}
			/// Get Owner
			if (!(((Enterprise)this.Get(documentId)).Owner == null))
				results.Add((IRelation)new Relation(
												ERelationType.Owner,
												documentId,
												((Enterprise)this.Get(documentId)).Owner,
												null, null));	
			Log.Debug("GetEnterpriseRelations(" + documentId + ") - " + results.Count.ToString() + " relations.");
			return results;
		}

		/// \brief Return ArrayList of IRelation objects
		/// \param documentId
		/// \note Currently returns memberOf relations for enterprises contacts are a member of and an
		/// IsAlso if the contact is also an account
		protected override ArrayList GetContactRelations(string documentId)
		{
			ArrayList		results;
			object			rpcResults;
			Contact			contact;

			Log.Debug("GeContactRelations(" + documentId + ")");
			results = new ArrayList();
			contact = (Contact)this.Get(documentId);
			/// Get Enterprise contact is a member of
			rpcResults = serverProxy.GetLinksFrom(MOGI.Id(documentId));
			if (rpcResults is Array)
				foreach(XmlRpcStruct result in (Array)rpcResults)
					Console.WriteLine("GetLinksReturned {0}", result.ToString());
			rpcResults = serverProxy.GetContactEnterprises(MOGI.Id(documentId));
			if (rpcResults is Array)
				if (((Array)rpcResults).Length > 0)
					foreach(XmlRpcStruct result in (Array)rpcResults)
					{
						string memberOfId;
						
						memberOfId = "Document/Enterprise/" + RPC2.ShortId(result["id"].ToString());
						Log.Debug("GetContactRelations(...) - MemberOf= " + memberOfId);
						IRelation relation = (IRelation) new Relation(
																	ERelationType.MemberOf,
																	documentId,
																	memberOfId,
																	null, null);
						Log.Debug("GetContactRelations(...) - Relation:" + relation.Name);
						results.Add(relation);
					}
			/// Get Account if contact is an account
			if (contact.IsAccount)
				results.Add((IRelation)new Relation(
												ERelationType.IsAlso,
												documentId,
												"Document/Account/" + MOGI.Id(documentId),
												null, null));
			/// Get Owner
			if (!(contact.Owner == null))
				results.Add((IRelation)new Relation(
												ERelationType.Owner,
												documentId,
												contact.Owner,
												null, null));
			return results;			
		}
		
		/// \brief Return ArrayList of IRelation objects
		/// \param documentId
		/// \note 
		/// \todo Return teams the account is a member of and the related
		/// contact object, plus any object links
		protected override ArrayList GetAccountRelations(string documentId)
		{
			ArrayList		results;
			ArrayList		rpcResults;
			Contact			contact;
			Account			account;
			
			results = new ArrayList();
			account = (Account)this.Get(documentId);
			contact = (Contact)this.Get("Document/Contact/" + MOGI.Id(documentId));
			if (!(contact == null))
				if (contact.IsAccount)
					results.Add((IRelation)new Relation(
												ERelationType.IsAlso,
												documentId,
												contact.Name,
												null, null));
			rpcResults = this.Search("Document/Team/member="  + account.Login);
			foreach(ITeam team in rpcResults)
				results.Add((IRelation)new Relation(
											ERelationType.MemberOf,
											documentId,
											team.Name,
											null, null));
			return results;
		}
		
		/// \brief Return ArrayList of IRelation objects
		/// \param documentId
		/// \note 
		/// \todo Return appointment participants, related resources, and
		/// any object links
		protected override ArrayList GetAppointmentRelations(string documentId)
		{
			ArrayList 	results;
			Appointment	appointment;
			
			results = new ArrayList();
			appointment = (Appointment)this.Get(documentId);
			// Get participants (as members)
			foreach(string member in appointment.Participants)
				results.Add((IRelation)new Relation(
												ERelationType.Member,
												documentId,
												member,
												null, null));
			// Get Owner
			if(!(appointment.Owner == null))
				results.Add((IRelation)new Relation(
												ERelationType.Owner,
												documentId,
												appointment.Owner,
												null, null));
			return results;
		}
		
		/// \brief Return ArrayList of IRelation objects
		/// \param documentId
		/// \note 
		/// \todo Return members of the team and any object links
		protected override ArrayList GetTeamRelations(string documentId)
		{
			ArrayList	results;
			results = new ArrayList();
			return results;
		}
		
		protected override string ServerGetName(string primaryKey) 
		{
			string entityName;
			
			if (primaryKey == null)
			{
				Log.Info("A NULL primary key was provided to ServerGetName.");
				throw new Exception(" NULL primary key was provided to ServerGetName.");
			}
			Log.Debug("ServerGetName( " + primaryKey + ")");
			try 
			{
				entityName = serverProxy.getTypeById(primaryKey.Trim());
			}	catch (XmlRpcFaultException xmlException)
				{ 
					Log.Info("RPC encoding error retrieving entity name");
					throw PrepareRPCException("RPC encoding error retrieving entity name", xmlException); 
				}
				catch (XmlRpcTypeMismatchException xmlException)
				{
				  Log.Info("RPC encoding error retrieving entity name");
					throw PrepareRPCException("RPC type mismatch retrieving entity name", xmlException);	 
				}
			switch (entityName.Trim())
			{
				case "Person": return "Document/Contact/" + primaryKey.Trim(); 
				case "Enterprise": return "Document/Enterprise/" + primaryKey.Trim();	
				case "Project": return "Document/Project/" + primaryKey.Trim(); 
				case "Job":	return "Document/Task/" + primaryKey.Trim(); 
				case "Date": return "Document/Appointment/" + primaryKey.Trim();
				case "Account": return "Document/Account/" + primaryKey.Trim(); 
				case "appointmentResource": return "Document/Resource/" + primaryKey.Trim();
				case "Team": return "Document/Team/" + primaryKey.Trim();
				case "Log": return "Document/Log/" + primaryKey.Trim();
				case "Doc": return "Document/Document/" + primaryKey.Trim();
			}
			Log.Info("Unexpected entity name returned by generic.getTypeById: " + entityName);
			throw new NoTypeException();
		}
	}
}
